/**
 *  Copyright 2007-2008 University Of Southern California
 *
 *  Licensed under the Apache License, Version 2.0 (the "License");
 *  you may not use this file except in compliance with the License.
 *  You may obtain a copy of the License at
 *
 *  http://www.apache.org/licenses/LICENSE-2.0
 *
 *  Unless required by applicable law or agreed to in writing,
 *  software distributed under the License is distributed on an "AS IS" BASIS,
 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  See the License for the specific language governing permissions and
 *  limitations under the License.
 */


package edu.isi.pegasus.planner.classes;

import edu.isi.pegasus.planner.cluster.JobAggregator;

import edu.isi.pegasus.planner.partitioner.graph.Graph;
import edu.isi.pegasus.planner.partitioner.graph.GraphNode;
import edu.isi.pegasus.planner.partitioner.graph.MapGraph;
import java.util.List;
import java.util.Iterator;
import java.util.LinkedList;

/**
 * This class holds all the specifics of an aggregated job. An aggregated job
 * or a clustered job is a job, that contains a collection of smaller jobs.
 * An aggregated job during execution may explode into n smaller job executions.
 * At present it does not store information about the dependencies between the
 * jobs.
 *
 * @author Karan Vahi
 * @version $Revision$
 */

public class AggregatedJob extends Job implements Graph{

    /**
     * The collection of jobs that are contained in the aggregated job.
     */
//    private List mConstituentJobs;

    /**
     * Boolean indicating whether a job has been fully rendered to an executable
     * job or not i.e the aggregated job has been mapped to the aggregator and
     * the constituent jobs have been gridstarted or not.
     */
    private boolean mHasBeenRenderedToExecutableForm;

    /**
     * Handle to the JobAggregator that created this job.
     */
    private JobAggregator mJobAggregator;


    /**
     * Handle to the Graph implementor.
     */
    private Graph mGraphImplementor;

    /**
     * The default constructor.
     */
    public AggregatedJob() {
        super();
//        mConstituentJobs = new ArrayList(3);
        mHasBeenRenderedToExecutableForm = false;
        this.mJobAggregator = null;
        mGraphImplementor = new MapGraph();
    }

    /**
     * The overloaded constructor.
     *
     * @param num  the number of constituent jobs
     */
    public AggregatedJob(int num) {
        this();
//        mConstituentJobs = new ArrayList(num);
    }

    /**
     * The overloaded constructor.
     *
     * @param job the job whose shallow copy is created, and is the main job.
     * @param num the number of constituent jobs.
     */
    public AggregatedJob(Job job,int num) {
        super((Job)job.clone());
//        mConstituentJobs = new ArrayList(num);
        mHasBeenRenderedToExecutableForm = false;
        this.mJobAggregator = null;
        this.mGraphImplementor = new MapGraph();
    }

    /**
     * Returns a boolean indicating whether a job has been rendered to an executable
     * form or not
     *
     * @return boolean
     */
    public boolean renderedToExecutableForm( ){
        return this.mHasBeenRenderedToExecutableForm;
    }


    /**
     * Returns a boolean indicating whether a job has been rendered to an executable
     * form or not
     *
     * @param value boolean to set to.
     */
    public void setRenderedToExecutableForm( boolean value ){
        this.mHasBeenRenderedToExecutableForm = value;
    }

    /**
     * Sets the JobAggregator that created this aggregated job.
     * Useful for rendering the job to an executable form later on.
     *
     * @param aggregator   handle to the JobAggregator used for aggregating the job
     */
    public void setJobAggregator( JobAggregator aggregator ){
        this.mJobAggregator = aggregator;
    }

    /**
     * Returns the JobAggregator that created this aggregated job.
     * Useful for rendering the job to an executable form later on.
     *
     * @return  JobAggregator
     */
    public JobAggregator getJobAggregator( ){
        return this.mJobAggregator;
    }

    /**
     * Adds a job to the aggregated job.
     *
     * @param job  the job to be added.
     */
    public void add(Job job){
//        mConstituentJobs.add(job);

        //should be getID() instead of logicalID
        //however that requires change in vertical clusterer where
        //partition object is indexed by logical id's
        this.addNode(new GraphNode( job.getLogicalID(), job ) );
    }

    /**
     * Clustered jobs never originate in the DAX. Always return null.
     * 
     * @return  null
     */
    public String getDAXID(){
        return null;
    }
    
    /**
     * Returns a new copy of the Object. The constituent jobs are also cloned.
     *
     * @return Object
     */
    public Object clone(){
        AggregatedJob newJob = new AggregatedJob((Job)super.clone(),
                                              this.size());
        /*
        for(Iterator it = this.mConstituentJobs.iterator();it.hasNext();){
            newJob.add( (Job)(((Job)it.next()).clone()));
        }*/
        //shallow clone. Fix me.
        newJob.mGraphImplementor = (Graph)((MapGraph)this.mGraphImplementor).clone();


        newJob.mHasBeenRenderedToExecutableForm = this.mHasBeenRenderedToExecutableForm;
        newJob.mJobAggregator = this.mJobAggregator;

        return newJob;
    }


    /**
     * Returns an iterator to the constituent jobs of the AggregatedJob.
     *
     * @return Iterator
     */
    public Iterator<Job> constituentJobsIterator(){
//        return mConstituentJobs.iterator();
        //need to use the toplogical sort iterator for label based clustering
        List<Job> l = new LinkedList();
        for( Iterator<GraphNode> it = this.nodeIterator(); it.hasNext(); ){
           GraphNode n = it.next();
           l.add( (Job)n.getContent() );
        }
        return l.iterator();
    }
    
    /**
     * Returns a job from a particular position in the list of constituent jobs
     * 
     * @param index    the index to retrieve from
     *
     * @return   a constituent job.
     */
    public Job getConstituentJob( int index ){
//        return (Job) this.mConstituentJobs.get( index );
       
        //should be deprecated
        int i =0;
        for( Iterator<GraphNode> it = this.nodeIterator(); it.hasNext(); i++ ){
            GraphNode n = it.next();
            if( i == index ){
                return (Job)n.getContent();
            }
        }
        return null;
    }
    
    /**
     * Returns the number of constituent jobs.
     *
     * @return Iterator
     */
    public int numberOfConsitutentJobs(){
//        return mConstituentJobs.size();
        return this.size();
    }
    /**
     * Returns a textual description of the object.
     *
     * @return textual description of the job.
     */
    public String toString(){
        StringBuffer sb = new StringBuffer(32);
        sb.append("\n").append("[MAIN JOB]").append(super.toString());
        sb.append("\n").append("[CONSTITUENT JOBS]");
        int num = 0;
        for(Iterator it = this.nodeIterator();it.hasNext();++num){
            sb.append("\n").append("[CONSTITUENT JOB] :").append(num);
            sb.append(it.next());
        }
        return sb.toString();
    }

    /**
     * Adds a node to the Graph. It overwrites an already existing node with the
     * same ID.
     *
     * @param node  the node to be added to the Graph.
     */
    public void addNode(GraphNode node) {
        this.mGraphImplementor.addNode(node);
    }

    /**
     * Adds an edge between two already existing nodes in the graph.
     *
     * @param parent   the parent node ID.
     * @param child    the child node ID.
     */
    public void addEdge(String parent, String child) {
        this.mGraphImplementor.addEdge(parent, child);
    }

    /**
     * A convenience method that allows for bulk addition of edges between
     * already existing nodes in the graph.
     *
     * @param child   the child node ID
     * @param parents list of parent identifiers as <code>String</code>.
     */
    public void addEdges(String child, List parents) {
        this.mGraphImplementor.addEdges(child, parents);
    }

    /**
     * Returns the node matching the id passed.
     *
     * @param identifier  the id of the node.
     *
     * @return the node matching the ID else null.
     */
    public GraphNode getNode(String identifier) {
        return this.mGraphImplementor.getNode(identifier);
    }

    /**
     * Adds a single root node to the Graph. All the exisitng roots of the
     * Graph become children of the root.
     *
     * @param root  the <code>GraphNode</code> to be added as a root.
     *
     * @throws RuntimeException if a node with the same id already exists.
     */
    public void addRoot(GraphNode root) {
        this.mGraphImplementor.addRoot(root);
    }

    /**
     * Removes a node from the Graph.
     *
     * @param identifier   the id of the node to be removed.
     *
     * @return boolean indicating whether the node was removed or not.
     */
    public boolean remove(String identifier) {
        return this.mGraphImplementor.remove(identifier);
    }

     /**
     * Returns an iterator for the nodes in the Graph. These iterators are
     * fail safe.
     *
     * @return Iterator
     */
    public Iterator<GraphNode> nodeIterator() {
        return this.mGraphImplementor.nodeIterator();
    }

    /**
     * Returns an iterator that traverses through the graph using a graph
     * traversal algorithm.
     *
     * @return Iterator through the nodes of the graph.
     */
    public Iterator<GraphNode> iterator() {
        return this.mGraphImplementor.iterator();
    }

    /**
     * Returns an iterator for the graph that traverses in topological sort
     * order.
     *
     * @return Iterator through the nodes of the graph.
     */
    public Iterator<GraphNode> topologicalSortIterator() {
        return this.mGraphImplementor.topologicalSortIterator();
    }


    /**
     * Returns the number of nodes in the graph.
     */
    public int size() {
        return this.mGraphImplementor.size();
    }

    /**
     * Returns the root nodes of the Graph.
     *
     * @return  a list containing <code>GraphNode</code> corressponding to the
     *          root nodes.
     */
    public List<GraphNode> getRoots() {
        return this.mGraphImplementor.getRoots();
    }

    /**
     * Returns the leaf nodes of the Graph.
     *
     * @return  a list containing <code>GraphNode</code> corressponding to the
     *          leaf nodes.
     */
    public List<GraphNode> getLeaves() {
        return this.mGraphImplementor.getLeaves();
    }

    /**
     * Returns a boolean if there are no nodes in the graph.
     *
     * @return boolean
     */
    public boolean isEmpty() {
        return this.mGraphImplementor.isEmpty();
    }

}
